{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Noise Transformation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "This notebook shows how to use the Qiskit Aer utility functions `approximate_quantum_error` and `approximate_noise_model` to transform quantum noise channels into a different, more suitable, noise channel.\n",
    "\n",
    "Our guiding example is Clifford simulation. A Clifford simulator can efficiently simulate quantum computations which include gates only from a limited, non-universal set of gates (the Clifford gates). Not all quantum noises can be added to such simulations; hence, we aim to find a \"close\" noise channel which can be simulated in a Clifford simulator.\n",
    "\n",
    "We begin by importing the transformation functions from the Aer provider utilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.658036Z",
     "start_time": "2019-08-19T17:13:34.394912Z"
    }
   },
   "outputs": [],
   "source": [
    "from qiskit.providers.aer.utils import approximate_quantum_error\n",
    "from qiskit.providers.aer.utils import approximate_noise_model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The name \"approximate\" suggests that these functions generate the closest (in the Hilbert-Schmidt metric) error possible to the given one.\n",
    "\n",
    "We demonstrate the approximation using several standard error channels defined in Qiskit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.676647Z",
     "start_time": "2019-08-19T17:13:36.672883Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Import Aer QuantumError functions that will be used\n",
    "from qiskit.providers.aer.noise import amplitude_damping_error\n",
    "from qiskit.providers.aer.noise import reset_error\n",
    "from qiskit.providers.aer.noise import pauli_error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "A 1-qubit quantum channel is a function $\\mathcal{C}:\\mathbb{C}^{2\\times2}\\to\\mathbb{C}^{2\\times2}$ mapping density operators to density operators (to ensure the image is a density operator $\\mathcal{C}$ is required to be completely positive and trace preserving, **CPTP**).\n",
    "\n",
    "Given quantum channels $\\mathcal{E}_{1},\\dots,\\mathcal{E}_{r}$, and probabilities $p_1, p_2, \\dots, p_r$ such that $0\\le p_i \\le 1$ and $p_1+\\dots +p_r = 1$, a new quantum channel $\\mathcal{C}_\\mathcal{E}$ can be constructed such that $\\mathcal{C}_\\mathcal{E}(\\rho)$ has the effect of choosing the channel $\\mathcal{E}_i$ with probability $p_i$ and applying it to $\\rho$.\n",
    "\n",
    "The noise transformation function solves the following optimization problem: Given a channel $\\mathcal{C}$ (\"goal\") and a list of channels $\\mathcal{E}_{1},\\dots,\\mathcal{E}_{r}$, find the probabilities $p_1, p_2, \\dots, p_r$ minimizing $D(\\mathcal{C}, \\mathcal{C}_\\mathcal{E})$ according to some distance metric $D$ (the Hilbert-Schmidt metric is currently used).\n",
    "\n",
    "To ensure the approximation is honest, in the sense that the approximate error channel serves as an \"upper bound\" for the actual error channel, we add the additional honesty constraint:\n",
    "\n",
    "$$\\text{F}(I,\\mathcal{C})\\ge F(I,\\mathcal{C}_\\mathcal{E})$$\n",
    "\n",
    "Where $\\text{F}$ is a fidelity measure and $I$ is the identity channel."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: Approximating amplitude damping noise with reset noise.\n",
    "\n",
    "**Amplitude damping** noise is described by a single parameter $0\\le \\gamma \\le 1$ and given by the Kraus operators:\n",
    "\n",
    "$$\\left(\\begin{array}{cc}\n",
    "1 & 0\\\\\n",
    "0 & \\sqrt{1-\\gamma}\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & \\sqrt{\\gamma}\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right)$$\n",
    "\n",
    "**Reset** error is described by probabilities $0\\le p, q\\le 1$ such that $p+q\\le 1$ and given by the Kraus operators:\n",
    "\n",
    "$$\\left(\\begin{array}{cc}\n",
    "\\sqrt{p} & 0\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & \\sqrt{p}\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & 0\\\\\n",
    "\\sqrt{q} & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & 0\\\\\n",
    "0 & \\sqrt{q}\n",
    "\\end{array}\\right)$$\n",
    "\n",
    "This can be thought of as \"resetting\" the quantum state of the affected qubit to $\\left|0\\right\\rangle$ with probability $p$, to $\\left|1\\right\\rangle$ with probability $q$, and do nothing with probability $1-(p+q)$.\n",
    "\n",
    "It is not too difficult to determine analytically the best values of $p,q$ to approximate a $\\gamma$ amplitude damping channel, see the details __[here](https://arxiv.org/abs/1207.0046)__. The best approximation is:\n",
    "\n",
    "$$p=\\frac{1}{2}\\left(1+\\gamma-\\sqrt{1-\\gamma}\\right), q=0$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.791986Z",
     "start_time": "2019-08-19T17:13:36.704432Z"
    }
   },
   "outputs": [],
   "source": [
    "gamma = 0.23\n",
    "error = amplitude_damping_error(gamma)\n",
    "results = approximate_quantum_error(error, operator_string=\"reset\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We only needed the above code to perform the actual approximation. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.812282Z",
     "start_time": "2019-08-19T17:13:36.805559Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044614, QasmQobjInstructions = [[{'name': 'id', 'qubits': [0]}]\n",
      "  P(1) = 0.17625178069553862, QasmQobjInstructions = [[{'name': 'reset', 'qubits': [0]}]\n",
      "\n",
      "Expected results:\n",
      "P(0) = 0.8237482193696062\n",
      "P(1) = 0.17625178063039387\n",
      "P(2) = 0\n"
     ]
    }
   ],
   "source": [
    "print(results)\n",
    "\n",
    "p = (1 + gamma - np.sqrt(1 - gamma)) / 2\n",
    "q = 0\n",
    "\n",
    "print(\"\")\n",
    "print(\"Expected results:\")\n",
    "print(\"P(0) = {}\".format(1-(p+q)))\n",
    "print(\"P(1) = {}\".format(p))\n",
    "print(\"P(2) = {}\".format(q))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We got the results predicted analytically.\n",
    "\n",
    "## Different input types\n",
    "\n",
    "The approximation function is given two inputs: The error channel to approximate, and a set of error channels that can be used in constructing the approximation.\n",
    "\n",
    "The **error channel** to approximate can be given as any input that can be converted to the `QuantumError` object. \n",
    "\n",
    "As an example, we explicitly construct the Kraus matrices of amplitude damping and pass to the same approximation function as before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:37.367331Z",
     "start_time": "2019-08-19T17:13:37.325594Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044617, QasmQobjInstructions = [[{'name': 'id', 'qubits': [0]}]\n",
      "  P(1) = 0.1762517806955383, QasmQobjInstructions = [[{'name': 'reset', 'qubits': [0]}]\n"
     ]
    }
   ],
   "source": [
    "gamma = 0.23\n",
    "K0 = np.array([[1,0],[0,np.sqrt(1-gamma)]])\n",
    "K1 = np.array([[0,np.sqrt(gamma)],[0,0]])\n",
    "results = approximate_quantum_error((K0, K1), operator_string=\"reset\")\n",
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The **error operators** that are used to construct the approximating channel can be either given as a list, a dictionary or a string indicating hard-coded channels. \n",
    "\n",
    "Any channel can be either a list of Kraus operators, or 'QuantumError' objects.\n",
    "\n",
    "The identity channel does not need to be passed directly; it is always implicitly used.\n",
    "\n",
    "As an example, we approximate amplitude damping using an explicit Kraus representation for reset noises:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:38.083970Z",
     "start_time": "2019-08-19T17:13:38.046909Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044614, QasmQobjInstructions = [[{'name': 'id', 'qubits': [0]}]\n",
      "  P(1) = 0.17625178069553862, QasmQobjInstructions = [[{'name': 'kraus', 'qubits': [0], 'params': [array([[1, 0],\n",
      "       [0, 0]]), array([[0, 1],\n",
      "       [0, 0]])]}]\n"
     ]
    }
   ],
   "source": [
    "reset_to_0 = [np.array([[1,0],[0,0]]), np.array([[0,1],[0,0]])]\n",
    "reset_to_1 = [np.array([[0,0],[1,0]]), np.array([[0,0],[0,1]])]\n",
    "reset_kraus = (reset_to_0, reset_to_1)\n",
    "\n",
    "gamma = 0.23\n",
    "error = amplitude_damping_error(gamma)\n",
    "results = approximate_quantum_error(error, operator_list=reset_kraus)\n",
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note the difference in the output channel: The probabilities are the same, but the input Kraus operators were converted to general Kraus channels, which cannot be used in a Clifford simulator. Hence, it is always better to pass a `QuantumError` object instead of the Kraus matrices, when possible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:57.085790Z",
     "start_time": "2019-08-19T17:13:57.077225Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<h3>Version Information</h3><table><tr><th>Qiskit Software</th><th>Version</th></tr><tr><td>Qiskit</td><td>None</td></tr><tr><td>Terra</td><td>0.14.0</td></tr><tr><td>Aer</td><td>0.6.0</td></tr><tr><td>Ignis</td><td>None</td></tr><tr><td>Aqua</td><td>None</td></tr><tr><td>IBM Q Provider</td><td>0.6.1</td></tr><tr><th>System information</th></tr><tr><td>Python</td><td>3.7.7 (default, Mar 26 2020, 10:32:53) \n",
       "[Clang 4.0.1 (tags/RELEASE_401/final)]</td></tr><tr><td>OS</td><td>Darwin</td></tr><tr><td>CPUs</td><td>4</td></tr><tr><td>Memory (Gb)</td><td>16.0</td></tr><tr><td colspan='2'>Tue Apr 28 13:42:53 2020 EDT</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div style='width: 100%; background-color:#d5d9e0;padding-left: 10px; padding-bottom: 10px; padding-right: 10px; padding-top: 5px'><h3>This code is a part of Qiskit</h3><p>&copy; Copyright IBM 2017, 2020.</p><p>This code is licensed under the Apache License, Version 2.0. You may<br>obtain a copy of this license in the LICENSE.txt file in the root directory<br> of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.<p>Any modifications or derivative works of this code must retain this<br>copyright notice, and modified files need to carry a notice indicating<br>that they have been altered from the originals.</p></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import qiskit.tools.jupyter\n",
    "%qiskit_version_table\n",
    "%qiskit_copyright"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
